package com.google.code.rbta.library;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Date;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * Target path is defined fellow:
 * <p>
 * If there is a SD-Card,
 * <p>
 * we save the targets into SD_DIR/your.app.com/file_downloader/
 * <p>
 * else
 * <p>
 * /data/data/your.app.com/file_downloader/
 * 
 * @author zhaoxinyue
 * 
 */
public class FileDownloader extends AsyncTask<Uri, Exception, Exception>
{
    private String              mUserAgent;
    private DefaultHttpClient   mHttpClient;
    private HttpGet             mRequest;
    private Handler             mHandler;

    private static final String TAG       = FileDownloader.class.getSimpleName();
    private static final int    CELL_SIZE = 1024;

    public static final int     FD_START  = 1000;
    public static final int     FD_DONE   = 1001;
    public static final int     FD_FAILED = 1002;

    private boolean             sdcardAvailabilityDetected;
    private boolean             sdcardAvailable;
    private long                mFileSize;
    private Uri                 mFromUri;
    private File                mToFile;
    private Context             mContext;

    private FileDownloader( Context _cxt, Uri _from, Handler _handler )
    {
        mContext = _cxt;
        mFromUri = _from;
        mHandler = _handler;

        Internet internet = Internet.createInstance( mContext.getApplicationContext() );
        mUserAgent = internet.getUserAgent();
        mHttpClient = internet.getHttpClient();
    }

    public static void download( Context _cxt, Uri _from, Handler _handler )
    {
        new FileDownloader( _cxt, _from, _handler ).execute();
    }

    @Override
    protected void onPreExecute()
    {
        if( mHandler != null )
            mHandler.sendEmptyMessage( FD_START );
    }

    @Override
    protected Exception doInBackground( Uri... _params )
    {
        mRequest = new HttpGet( mFromUri.toString() );
        if( mUserAgent != null )
            mRequest.setHeader( "User-Agent", mUserAgent );
        mRequest.setHeader( "Accept-Encoding", "gzip" );
        mRequest.setHeader( "Keep-Alive", "open" );

        try
        {
            HttpResponse response = mHttpClient.execute( mRequest );
            if( response == null )
                return null;
            int statusCode = response.getStatusLine().getStatusCode();
            HttpEntity entity = response.getEntity();
            if( statusCode == HttpStatus.SC_OK && entity != null )
            {
                mFileSize = entity.getContentLength();
                if( this.mFileSize <= 0 )
                {
                    return new RuntimeException( "File size is less than 0." );
                }
                mToFile = new File( getTargetPath(), mFromUri.getLastPathSegment() );

                Log.i( TAG, "##############################################" );
                Log.i( TAG, "#     Start: Thread Reading R/W File         #" );
                Log.i( TAG, "##############################################" );
                InputStream instream = entity.getContent();
                writeFile( instream );
                instream.close();
                return null;
            }
            else
                return new RuntimeException();
        }
        catch( Exception _e )
        {
            return _e;
        }
    }

    @Override
    protected void onPostExecute( Exception _result )
    {
        if( _result != null )
        {
            cancelRequest();
            Log.e( TAG, "##############################################" );
            Log.e( TAG, "#     End: File R/W Faild                    #" );
            Log.e( TAG, "##############################################" );
            if( mHandler != null )
            {
                Message msg = Message.obtain();
                msg.what = FD_FAILED;
                msg.obj = _result;
                mHandler.sendMessage( msg );
            }
        }
        else
        {
            Log.i( TAG, "##############################################" );
            Log.i( TAG, "#     End: File R/W successfully             #" );
            Log.i( TAG, "##############################################" );
            if( mHandler != null )
                mHandler.sendEmptyMessage( FD_DONE );
        }
    }

    private void writeFile( InputStream _instream ) throws FileNotFoundException, IOException
    {
        byte[] buffer = new byte[CELL_SIZE];
        int offset = 0;
        RandomAccessFile wrotenFile = new RandomAccessFile( mToFile, "rwd" );
        wrotenFile.seek( 0 );
        while( (offset = _instream.read( buffer, 0, 1024 )) != -1 )
        {
            wrotenFile.write( buffer, 0, offset );
        }
        wrotenFile.close();
    }

    @Override
    protected void onCancelled()
    {
        cancelRequest();
    }

    private void cancelRequest()
    {
        if( mRequest != null && !mRequest.isAborted() )
            mRequest.abort();
    }

    private boolean isSDCardAvailable()
    {
        if( !sdcardAvailabilityDetected )
        {
            sdcardAvailable = detectSDCardAvailability();
            sdcardAvailabilityDetected = true;
        }
        return sdcardAvailable;
    }

    private synchronized boolean detectSDCardAvailability()
    {
        boolean result = false;
        try
        {
            Date now = new Date();
            long times = now.getTime();
            String fileName = "/sdcard/" + times + ".test";
            File file = new File( fileName );
            result = file.createNewFile();
            file.delete();
        }
        catch( Exception e )
        {
            Log.e( TAG, "SD card can't be used!" );
        }
        finally
        {
            sdcardAvailabilityDetected = true;
            sdcardAvailable = result;
        }
        return result;
    }

    private String getTargetPath() throws IOException
    {
        StringBuilder dirName = new StringBuilder();
        if( isSDCardAvailable() )
        {
            dirName.append( Environment.getExternalStorageDirectory() );
            dirName.append( '/' );
            dirName.append( mContext.getPackageName() );
            dirName.append( "/file_downloader/" );
        }
        else
        {
            dirName.append( "/data/data/" );
            dirName.append( mContext.getPackageName() );
            dirName.append( "/file_downloader/" );
        }

        File dir = new File( dirName.toString() );
        if( !dir.exists() || !dir.isDirectory() )
        {
            if( !dir.mkdirs() )
                throw new IOException( "Can't create target directory to store downloaded files." );
        }

        return dirName.toString();
    }
}
